package com.smartalarmclock.Model;

import java.util.HashSet;

import com.smartalarmclock.Model.Exceptions.InvalidDeleteDataEntityOperationException;
import com.smartalarmclock.Model.Exceptions.InvalidSaveDataEntityOperationException;


import android.content.Context;

/**
 * This class handles saving and deleting of a referenced data entity
 * @param <T> the concrete type the the referenced data entity
 */
abstract class Reference<T extends IReferencedDataEntity> {
	
	// Members
	
	protected Class<?> m_referencedType;
	protected DeleteMethod m_DeleteMethod;
	protected HashSet<T> m_OldReferences;
	
	/**
	 * The delete method when the referencing data entity calls the delete method
	 */
	public enum DeleteMethod {
		/**
		 * Do not delete. Choose this if the referenced data entity may be referenced by more than one entity.
		 */
		NoAction,
		
		/**
		 * Delete since the referenced data entity is used only by the current referencing entity.
		 */
		Delete
	}
	
	// Constructors
	
	/**
	 * Constructor
	 * @param deleteMethod the method to use when the referencing entity is deleted.
	 */
	protected Reference(Class<?> referencedType, DeleteMethod deleteMethod) {
		m_referencedType = referencedType;
		m_DeleteMethod = deleteMethod;
		m_OldReferences = new HashSet<T>();
	}
	
	// Protected Methods
	
	/**
	 * Deletes all old references from the DB.
	 * @param context the context of the request
	 */
	protected void DeleteAllOldReferences(Context context) {
		if (m_DeleteMethod == DeleteMethod.Delete) {
			for (T oldReference : m_OldReferences) {
				try {
					oldReference.Delete(context);
				}
				catch (InvalidDeleteDataEntityOperationException e) {
					// This cannot happen since the delete method is "Delete", which means that
					// there are no other entities referencing this entity. So the delete operation cannot fail.
				}
				catch (InvalidSaveDataEntityOperationException e) {
					// This cannot happen since the old reference is not referenced by any other referencing
					// entity (because the delete method is to delete the reference),
					// so there is no referencing entity to save.
				}
			}
		}
		
		m_OldReferences.clear();
	}
	
	/**
	 * Unsubscribes the referencing entity from the old reference and saves it in the old references set so we can delete
	 * it afterwards.
	 * @param oldReference the old reference
	 * @param referencingEntity the entity referencing the reference
	 */
	protected void HandleOldReference(T oldReference, IReferencingDataEntity referencingEntity) {
		// Holding the old referenced entities so we can delete them later when the user saves the entity in the DB.
		// We want to prevent deleting and adding the same referenced entities if the user chooses to set the references often.  
		if (oldReference != null) {
			m_OldReferences.add(oldReference);
			oldReference.UnregisterReference(referencingEntity);
		}
	}
	
	/**
	 * Subscribes the referencing entity to the new reference and removes it from the old references set
	 * if it exists in it.
	 * @param newReference the new reference
	 * @param referencingEntity the entity referencing the reference
	 */
	protected void HandleNewReference(T newReference, IReferencingDataEntity referencingEntity) {
		// Removing the new referenced entity from the list if it exists so when we
		// delete the old entities, we won't delete the new one also by mistake.
		if (newReference != null) {
			m_OldReferences.remove(newReference);
			newReference.RegisterReference(referencingEntity);
		}
	}
	
	// Abstract Methods
	
	/**
	 * Saves the referenced entities.
	 * @param context the context of the request
	 * @throws InvalidSaveDataEntityOperationException if the save operation is invalid.
	 */
	public abstract void SaveReferences(Context context) throws InvalidSaveDataEntityOperationException;
	
	/**
	 * Deletes the referenced data entity from the DB only if the delete method is to delete
	 * (the delete method should be set to delete if each reference is referenced by one referencing entity only)
	 * @param context the context of the request
	 */
	public abstract void OnDeleteReferencingEntity(Context context);
}
